/* Copyright (c) 2023 Renmin University of China
RMDB is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan PSL v2.
You may obtain a copy of Mulan PSL v2 at:
        http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details. */

#include "analyze.h"
#include "common/common.h"
#include "defs.h"
#include "errors.h"
#include "execution/execution_manager.h"
#include "optimizer/optimizer.h"
#include "optimizer/plan.h"
#include "parser/ast.h"
#include "portal.h"
#include <memory>
#include <vector>
bool judgeTypeEQ(ColType rhs, ColType lhs){
    // int和float可以相容
    return lhs == rhs || (lhs == TYPE_INT && rhs == TYPE_FLOAT) || (lhs == TYPE_FLOAT && rhs == TYPE_INT);
}
/**
 * @description: 分析器，进行语义分析和查询重写，需要检查不符合语义规定的部分
 * @param {shared_ptr<ast::TreeNode>} parse parser生成的结果集
 * @return {shared_ptr<Query>} Query 
 */
std::shared_ptr<Query> Analyze::do_analyze(std::shared_ptr<ast::TreeNode> parse, std::unique_ptr<Optimizer>& optimizer, std::unique_ptr<Portal>& portal)
{
    std::shared_ptr<Query> query = std::make_shared<Query>();
    if (auto x = std::dynamic_pointer_cast<ast::SelectStmt>(parse))
    {
        query->tables = std::move(x->tabs);
        for(auto& table_name : query->tables){
            if (!sm_manager_->db_.is_table(table_name)){
                throw TableNotFoundError(table_name);
            }
        }

        // 健壮性测试
        if(x->group != nullptr && x->group->cols.empty()){
            throw GroupNotFoundError();
        }

        // 放入选中的列到query
        for (auto &sv_sel_col : x->cols) {
            if(sv_sel_col->group_by_cond != ast::NONE)
                query->has_group= true;
            else
                query->has_group = false;
            TabCol temp_col = {
                .tab_name = sv_sel_col->tab_name,
                .col_name = sv_sel_col->col_name,
                .alias = sv_sel_col->alias,
                .group_by_cond = sv_sel_col->group_by_cond
            };
            query->cols.push_back(temp_col);
        }

        std::vector<ColMeta> all_cols;
        get_all_cols(query->tables, all_cols);
        if (query->cols.empty()) {
            for (auto &col : all_cols) {
                TabCol sel_col = {.tab_name = col.tab_name, .col_name = col.name, .alias = "", .group_by_cond = ast::NONE};
                query->cols.push_back(sel_col);
            }
        } else {
            for (auto &sel_col : query->cols) {
                sel_col = check_column(all_cols, sel_col);
            }
        }

        if (x->group)
        {
            for (auto &sv_group_col : x->group->cols) {
                TabCol group_col = {
                    .tab_name = sv_group_col->tab_name,
                    .col_name = sv_group_col->col_name,
                    .alias = sv_group_col->alias,
                    .group_by_cond = sv_group_col->group_by_cond
                };
                query->group_cols.push_back(group_col);
            }
    
            get_clause(x->group->conds, query->having_conds,optimizer, portal);
            for(auto &cond : query->having_conds)
                cond.is_having = true;
            check_clause(query->tables, query->having_conds);
        
            for(auto &col : query->group_cols){
                col = check_column(all_cols, col);
            }

            for (const auto& col : query->cols) {
                if (col.group_by_cond == ast::NONE) {
                    if (std::none_of(query->group_cols.begin(), query->group_cols.end(), [&](const TabCol& group_col) {
                            return group_col.col_name == col.col_name && group_col.tab_name == col.tab_name;
                        })) {
                        throw GroupNotFoundError();
                    }
                }
            }
        }

        // 放入where条件到query
        get_clause(x->conds, query->conds, optimizer, portal);
        check_clause(query->tables, query->conds);
    } else if (auto x = std::dynamic_pointer_cast<ast::UpdateStmt>(parse)) {
        query->tables.push_back(x->tab_name);
        if (!sm_manager_->db_.is_table(x->tab_name)){
            throw TableNotFoundError(x->tab_name);
        }
        get_clause(x->conds, query->conds, optimizer, portal);
        check_clause(query->tables, query->conds);
        for(const auto& clause : x->set_clauses){
            query->set_clauses.push_back({
                .lhs={.tab_name = query->tables.at(0),.col_name= clause->col_name},
                .rhs=convert_sv_value(clause->val),
                .rhs_col = {.tab_name = query->tables.at(0), .col_name = clause->value_col}, 
                .is_val= clause->is_val
            });
        }
        
        TabMeta table = sm_manager_->db_.get_table(query->tables.at(0));
        for(auto& clause: query->set_clauses){
        table.is_col(clause.lhs.col_name);
        ColType lhs_type = table.get_col(clause.lhs.col_name)->type;
        ColType rhs_type = clause.rhs.type;
        if(!judgeTypeEQ(lhs_type, rhs_type)){
            throw IncompatibleTypeError(coltype2str(lhs_type), coltype2str(rhs_type));
        }
        if(lhs_type == TYPE_INT && rhs_type == TYPE_FLOAT){
            clause.rhs.int_val = int(clause.rhs.float_val);
            clause.rhs.type = TYPE_INT;
        }else if(lhs_type == TYPE_FLOAT && rhs_type == TYPE_INT){
            clause.rhs.float_val = float(clause.rhs.int_val);
            clause.rhs.type = TYPE_FLOAT;
        }
    }

    } else if (auto x = std::dynamic_pointer_cast<ast::DeleteStmt>(parse)) {
        //处理where条件
        query->tables.push_back(x->tab_name);
        if (!sm_manager_->db_.is_table(x->tab_name)){
            throw TableNotFoundError(x->tab_name);
        }
        get_clause(x->conds, query->conds, optimizer, portal);
        check_clause({x->tab_name}, query->conds);
    } else if (auto x = std::dynamic_pointer_cast<ast::InsertStmt>(parse)) {
        // 处理insert 的values值
        for (auto &sv_val : x->vals) {
            query->values.push_back(convert_sv_value(sv_val));
        }
    } else if (auto x = std::dynamic_pointer_cast<ast::LoadStmt>(parse)) {
        query->file_path = x->file_path;
    } else {
        // do nothing
    }
    query->parse = std::move(parse);
    return query;
}

TabCol Analyze::check_column(const std::vector<ColMeta> &all_cols, TabCol target) {
    if (target.tab_name.empty()) {
        // Table name not specified, infer table name from column name
        std::string tab_name;
        for (auto &col : all_cols) {
            if (col.name == target.col_name) {
                if (!tab_name.empty()) {
                    throw AmbiguousColumnError(target.col_name);
                }
                tab_name = col.tab_name;
            }
        }
        if(target.group_by_cond == ast::COND_COUNT && target.col_name == "*"){
            return target;
        }
        if (tab_name.empty()) {
            throw ColumnNotFoundError(target.col_name);
        }
        target.tab_name = tab_name;
    } else {
        /** Make sure target column exists */
        TabMeta table = sm_manager_->db_.get_table(target.tab_name);
        if(!table.is_col(target.col_name)){
            throw ColumnNotFoundError(target.col_name);
        }
    }
    return target;
}

void Analyze::get_all_cols(const std::vector<std::string> &tab_names, std::vector<ColMeta> &all_cols) {
    for (auto &sel_tab_name : tab_names) {
        const auto &sel_tab_cols = sm_manager_->db_.get_table(sel_tab_name).cols;
        all_cols.insert(all_cols.end(), sel_tab_cols.begin(), sel_tab_cols.end());
    }
}

void Analyze::get_clause(const std::vector<std::shared_ptr<ast::BinaryExpr>> &sv_conds, std::vector<Condition> &conds,
        std::unique_ptr<Optimizer>& optimizer, std::unique_ptr<Portal>&portal) {
    conds.clear();
    for (auto &expr : sv_conds) {
        Condition cond;
        std::cout << "expr->lhs->col_name: " << expr->lhs->col_name.c_str() << std::endl;
        cond.lhs_col = {
            .tab_name = expr->lhs->tab_name, 
            .col_name = expr->lhs->col_name, 
            .alias = expr->lhs->alias,
            .group_by_cond = expr->lhs->group_by_cond
        };
        cond.op = convert_sv_comp_op(expr->op);
        if (auto rhs_val = std::dynamic_pointer_cast<ast::Value>(expr->rhs)) {
            cond.is_rhs_val = true;
            cond.rhs_val = convert_sv_value(rhs_val);
        } else if (auto rhs_col = std::dynamic_pointer_cast<ast::Col>(expr->rhs)) {
            cond.is_rhs_val = false;
            cond.rhs_col = {
                .tab_name = rhs_col->tab_name,
                .col_name = rhs_col->col_name,
                .alias = rhs_col->alias,
                .group_by_cond = rhs_col->group_by_cond
            };
        } else if(auto rhs_sel = std::dynamic_pointer_cast<ast::Select>(expr->rhs)){
            cond.is_rhs_val = true;
            cond.rhs_sel = Select(rhs_sel->select_stmt_);
            std::shared_ptr<Query> query = do_analyze(cond.rhs_sel.sel, optimizer, portal);
            std::shared_ptr<Plan> plan = optimizer->plan_query(query, nullptr);
            std::shared_ptr<PortalStmt> portal_stmt = portal->start(plan, nullptr);
            std::vector<std::shared_ptr<ast::Value>> values = portal->run_cond(portal_stmt);
            if(cond.op == OP_IN){
                cond.rhs_val.type = TYPE_MULTI;
                for(std::shared_ptr<ast::Value> v : values){
                    cond.rhs_val.values.push_back(convert_sv_value(v));
                }
                cond.is_sel = true;
            }else if(values.size() > 1){
                throw InternalError("values.size() != 1");
            }else if(values.size() < 1){
                cond.op = FALSE;
            }else
                cond.rhs_val = convert_sv_value(values[0]);
        } else if(auto rhs_in = std::dynamic_pointer_cast<ast::InValues>(expr->rhs)){
            cond.is_rhs_val = true;
            if(cond.op == OP_IN){
                cond.rhs_val.type = TYPE_MULTI;
                for(std::shared_ptr<ast::Value> v : rhs_in->values){
                    cond.rhs_val.values.push_back(convert_sv_value(v));
                }
                cond.is_sel = true;
            }
        }
        conds.push_back(cond);
    }
}


void Analyze::check_clause(const std::vector<std::string> &tab_names, std::vector<Condition> &conds) {
    // auto all_cols = get_all_cols(tab_names);
    std::vector<ColMeta> all_cols;
    get_all_cols(tab_names, all_cols);
    // Get raw values in where clause
    for (auto &cond : conds) {

        // judge group
        if(cond.is_having == false && (cond.lhs_col.group_by_cond != ast::NONE)){
            throw GroupNotFoundError();
        }

        if(!cond.is_rhs_val && cond.rhs_col.group_by_cond != ast::NONE && !cond.is_sel){
            throw GroupNotFoundError();
        }

        // Infer table name from column name
        cond.lhs_col = check_column(all_cols, cond.lhs_col);
        if (!cond.is_rhs_val && !cond.is_sel) {
            cond.rhs_col = check_column(all_cols, cond.rhs_col);
        }

        if (cond.lhs_col.group_by_cond == ast::COND_COUNT && cond.lhs_col.col_name == "*") {
            ColType lhs_type = TYPE_INT;
            ColType rhs_type;
            if (cond.is_rhs_val) {
                cond.rhs_val.init_raw(sizeof(int));
                rhs_type = cond.rhs_val.type;
            } else {
                rhs_type = sm_manager_->db_.get_table(cond.rhs_col.tab_name).get_col(cond.rhs_col.col_name)->type;
            }
            if(!judgeTypeEQ(lhs_type, rhs_type)){
                throw IncompatibleTypeError(coltype2str(lhs_type), coltype2str(rhs_type));
            }
        } else{

            TabMeta &lhs_tab = sm_manager_->db_.get_table(cond.lhs_col.tab_name);
            auto lhs_col = lhs_tab.get_col(cond.lhs_col.col_name);
            ColType lhs_type = lhs_col->type;
            ColType rhs_type;
            if(cond.is_sel)
                return;
            if (cond.is_rhs_val) {

                if(cond.op == FALSE)
                    continue;
                
                if(cond.op == OP_IN){
                    for(auto v : cond.rhs_val.values){
                        if(lhs_col->type == TYPE_STRING && (v.type == TYPE_INT || v.type == TYPE_FLOAT)){
                            throw InternalError("string type cannot be compared with int or float");
                        }else if((lhs_col->type == TYPE_INT || lhs_col->type == TYPE_FLOAT) && v.type == TYPE_STRING){
                            throw InternalError("int or float type cannot be compared with string");
                        }
                        v.init_raw(lhs_col->len);
                    }
                    continue;
                }

                if(lhs_col->type == TYPE_STRING && (cond.rhs_val.type == TYPE_INT || cond.rhs_val.type == TYPE_FLOAT)){
                    throw InternalError("string type cannot be compared with int or float");
                }else if((lhs_col->type == TYPE_INT || lhs_col->type == TYPE_FLOAT) && cond.rhs_val.type == TYPE_STRING){
                    throw InternalError("int or float type cannot be compared with string");
                }

                cond.rhs_val.init_raw(lhs_col->len);
                rhs_type = cond.rhs_val.type;
            } else {
                TabMeta &rhs_tab = sm_manager_->db_.get_table(cond.rhs_col.tab_name);
                auto rhs_col = rhs_tab.get_col(cond.rhs_col.col_name);
                rhs_type = rhs_col->type;
            }
            if(!judgeTypeEQ(lhs_type, rhs_type)){
                throw IncompatibleTypeError(coltype2str(lhs_type), coltype2str(rhs_type));
            }

        }
    }
}


Value Analyze::convert_sv_value(const std::shared_ptr<ast::Value> &sv_val) {
    Value val;
    if (auto int_lit = std::dynamic_pointer_cast<ast::IntLit>(sv_val)) {
        val.set_int(int_lit->val);
    } else if (auto float_lit = std::dynamic_pointer_cast<ast::FloatLit>(sv_val)) {
        val.set_float(float_lit->val);
    } else if (auto str_lit = std::dynamic_pointer_cast<ast::StringLit>(sv_val)) {
        val.set_str(str_lit->val);
    } else {
        throw InternalError("Unexpected sv value type");
    }
    return val;
}

CompOp Analyze::convert_sv_comp_op(ast::SvCompOp op) {
    static const std::map<ast::SvCompOp, CompOp> m = {
        {ast::SV_OP_EQ, OP_EQ}, {ast::SV_OP_NE, OP_NE}, {ast::SV_OP_LT, OP_LT},
        {ast::SV_OP_GT, OP_GT}, {ast::SV_OP_LE, OP_LE}, {ast::SV_OP_GE, OP_GE},
        {ast::SV_OP_IN, OP_IN},{ast::SV_OP_IN, OP_IN},
    };
    return m.at(op);
}
